﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using EssIL;
using OmniKits.Core;
using System.Collections;
using System.Threading;
using System.Diagnostics;

#if NanaTubeLib
namespace OmniKits.NanaTube
#else
namespace OmniKits.Async
#endif
{
    public partial class NanaTubeAsync<T>
      : DisposableBase, IEnumerable<T>, IEnumerable<Task<T>>
    {
#pragma warning disable 1591

        private volatile Holder _TopHolder;

        #region constructor and Dispose

        public NanaTubeAsync()
        {
            Memory.NormalWrite(out _TopHolder, new Holder());
        }

        protected override void Dispose(bool disposing)
        {
            // TODO: when NanaTube is finalized, the holder which is actually hold by enumerators is not, so how do we notify them?

#pragma warning disable 0420
            var holder = Interlocked.Exchange(ref _TopHolder, null);
#pragma warning restore 0420
            if (holder != null)
            {
                holder.Dispose();
            }
        }

        public void Close()
        {
            this.Dispose();
        }

        #endregion

        #region read & write

        public void Push(T data)
        {
            var @new = new Holder();
            Holder top4check;

            // only do the work if the top of the chain is not null
            while ((top4check = _TopHolder) != null)
            {
#pragma warning disable 0420
                // ensure that we won't replace null with a new value
                var top = Interlocked.CompareExchange(ref _TopHolder, @new, top4check);
#pragma warning restore 0420

                // so this means we now have the last holder
                if (top == top4check)
                {
                    top.Next = @new;
                    top.SetData(data);
                    return;
                }
            }

            throw new ObjectDisposedException("NanaTubeAsync");
        }

        #region enumerator reading

        /// <summary></summary>
        IEnumerator<T> IEnumerable<T>.GetEnumerator()
        {
            return new EnumContent(_TopHolder).DataEnumerator;
        }

        class EnumContent
        {
            private Holder _Current;
            private IEnumerator<Task<T>> _TaskEnumerator;
            internal IEnumerator<Task<T>> TaskEnumerator { get { return _TaskEnumerator; } }
            internal IEnumerator<T> DataEnumerator { get { return MakeDataEnumerator(_TaskEnumerator); } }

            public EnumContent(Holder current)
            {
                _Current = current;
                _TaskEnumerator = MakeTaskEnumerator();
            }

            private IEnumerator<Task<T>> MakeTaskEnumerator()
            {
                while (_Current != null)
                {
                    var task = _Current.Task;
                    yield return task;
                    task.Wait();
                    _Current = _Current.Next;
                }

            }
            private IEnumerator<T> MakeDataEnumerator(IEnumerator<Task<T>> taskEnumerator)
            {
                while (taskEnumerator.MoveNext())
                {
                    var task = taskEnumerator.Current;

                    if (task.IsCanceled)
                        yield break;

                    try
                    {
                        task.Wait();
                    }
                    catch (ObjectDisposedException)
                    {
                        yield break;
                    }
                    catch (AggregateException)
                    {
                        if (task.IsCanceled)
                            yield break;
                        throw;
                    }

                    yield return task.Result;
                }
            }
        }

        IEnumerator<Task<T>> IEnumerable<Task<T>>.GetEnumerator()
        {
            return new EnumContent(_TopHolder).TaskEnumerator;
        }

        IEnumerator IEnumerable.GetEnumerator() { return this.AsEnumerable<T>().GetEnumerator(); }

        #endregion

        #endregion
    }
}
